package com.gaj.util;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * Minio 工具类，用于与 Minio 对象存储服务进行交互。
 * 提供了文件的增删改查等基本操作，并支持生成预签名URL进行文件访问。
 * 提供了通过文件、流、路径和 URL 上传文件到 Minio 的功能。
 *
 * @author 赵志伟
 * @version 2.0
 */
@SuppressWarnings({"all"})
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioUtil {

    public static String MINIO_ENDPOINT;// Minio 服务器地址

    public static String MINIO_BUCKET_NAME;// Minio 存储桶的名称

    public static String MINIO_ACCESS_KEY;// Minio 访问密钥

    public static String MINIO_SECRET_KEY;// Minio 密钥

    private static MinioClient minioClient;

    // 日志记录器，用于记录日志信息
    private static Logger log = LoggerFactory.getLogger(MinioUtil.class);


    // 实例变量用于接收配置
    public String endpoint;

    public String bucketName;

    public String accessKey;

    public String secretKey;

    /**
     * 初始化方法，将配置值从实例变量赋给静态变量
     */
    @PostConstruct
    public void init() {
        MINIO_ENDPOINT = this.endpoint;
        MINIO_BUCKET_NAME = this.bucketName;
        MINIO_SECRET_KEY = this.secretKey;
        MINIO_ACCESS_KEY = this.accessKey;

        minioClient = MinioClient.builder()
                .endpoint(MINIO_ENDPOINT)
                .credentials(MINIO_ACCESS_KEY, MINIO_SECRET_KEY)
                .build();

        // 检查默认存储桶是否存在，不存在则创建
        ensureBucketExists();
    }

    /**
     * 通过图片 URL 上传文件到 Minio。
     *
     * @param imageUrl   图片的 URL。
     * @param bucketName Minio 存储桶的名称。
     * @param objectName 文件在 Minio 中存储的名称。
     * @return 文件在 Minio 存储中的 URL。
     */
    public static String uploadByUrl(String imageUrl, String bucketName, String objectName) {
        InputStream inputStream = null;
        HttpURLConnection connection = null;

        try {
            // 创建 URL 对象并打开连接
            URL url = new URL(imageUrl);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.connect();

            // 检查连接是否成功
            int responseCode = connection.getResponseCode();
            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw new RuntimeException("无法从 URL 下载图片: " + imageUrl);
            }

            // 获取输入流
            inputStream = connection.getInputStream();

            // 使用 Minio 客户端上传文件
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, connection.getContentLength(), -1) // 流上传
                            //.contentType("image/jpeg") // 假设图片是 JPEG 格式，实际场景可以动态检测
                            .contentType(connection.getContentType()) // 动态检测
                            .build()
            );

            // 获取文件的预签名 URL
            String urlInMinio = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .method(Method.GET)
                    .build());

            log.info("图片上传成功，Minio URL => {}", urlInMinio);

            // 返回不包含查询参数的 URL
            return urlInMinio.substring(0, urlInMinio.indexOf('?'));

        } catch (Exception e) {
            throw new RuntimeException("通过图片 URL 上传到 Minio 失败", e);
        } finally {
            // 确保关闭连接和输入流
            try {
                if (inputStream != null) inputStream.close();
                if (connection != null) connection.disconnect();
            } catch (Exception ex) {
                log.error("关闭连接时发生错误", ex);
            }
        }
    }

    /**
     * 通过文件的绝对路径上传文件到 Minio 存储，使用分块上传提高上传效率，并返回文件的 URL。
     *
     * @param filePath   要上传文件的绝对路径。
     * @param bucketName Minio 存储桶的名称。
     * @param objectName 文件在 Minio 中存储的名称。
     * @return 文件在 Minio 存储中的 URL。
     */
    public static String uploadByPath(String filePath, String bucketName, String objectName) {
        File file = new File(filePath);
        return upload(file, bucketName, objectName); // 调用 upload(File, String) 方法上传文件
    }

    /**
     * 通过文件流上传文件到 Minio 存储，使用分块上传提高上传效率，并返回文件的 URL。
     *
     * @param inputStream 文件的输入流。
     * @param bucketName  Minio 存储桶的名称。
     * @param objectName  文件在 Minio 中存储的名称。
     * @param contentType 文件类型，例如 "application/zip"。
     * @return 文件在 Minio 存储中的 URL。
     */
    public static String uploadByStream(InputStream inputStream, String bucketName, String objectName, String contentType) {
        try {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, -1, 10485760) // 使用流上传，限制最大内存占用（10MB）
                            .contentType(contentType)
                            .build()
            );

            // 获取预签名 URL
            String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .method(Method.GET)
                    .build());
            log.info("获取文件的预签名 URL 成功 => {}", url);
            return url.substring(0, url.indexOf('?')); // 返回不包含查询参数的 URL

        } catch (Exception e) {
            throw new RuntimeException("通过流上传文件到 Minio 失败", e);
        }
    }

    /**
     * 上传文件到 Minio 存储，使用分块上传提高上传效率，并返回文件的 URL。
     *
     * @param file   要上传的文件。
     * @param bucketName Minio 存储桶的名称。
     * @param objectName 文件在 Minio 中存储的名称。
     * @return 文件在 Minio 存储中的 URL。
     */
    public static String upload(File file, String bucketName, String objectName) {
        // 声明文件输入流变量
        FileInputStream fileInputStream = null;

        try {
            // 初始化文件输入流
            fileInputStream = new FileInputStream(file);

            // 使用 Minio 的分块上传功能，适合大文件上传，减少内存占用并提高上传效率。
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(file.getAbsolutePath()) // 直接上传文件，不需要手动读取文件流。
                            .contentType(detectContentType(file)) // 自动检测文件类型
                            .build()
            );

            // 获取文件的预签名 URL，用于访问存储在 Minio 中的文件。
            String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .method(Method.GET)
                    .build());
            // 去掉 URL 中的查询参数，仅保留基础路径。
            url = url.substring(0, url.indexOf('?'));
            log.info("获取文件的预签名 URL成功=>{}", url);

            return url;  // 返回文件的存储路径。
        } catch (Exception e) {
            throw new RuntimeException("上传压缩包到 Minio 失败", e);
        } finally {
            // 在finally块中手动关闭资源，确保资源释放
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (Exception e) {
                    log.error("关闭文件输入流失败", e);
                }
            }
        }
    }

    /**
     * 从 Minio 存储中删除文件。
     *
     * @param bucketName Minio 存储桶的名称。
     * @param objectName 要删除的对象名称。
     * @return 如果删除成功返回 true，否则返回 false。
     */
    public static boolean delete(String bucketName, String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            log.info("文件删除成功 => Bucket: {}, Object: {}", bucketName, objectName);
            return true;
        } catch (Exception e) {
            log.error("删除文件失败 => Bucket: {}, Object: {}, Error: {}", bucketName, objectName, e.getMessage());
            return false;
        }
    }

    /**
     * 列出指定存储桶中的所有文件。
     *
     * @param bucketName Minio 存储桶的名称。
     * @return 文件名称列表。
     */
    public static List<String> listFiles(String bucketName) {
        List<String> fileList = new ArrayList<>();
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                fileList.add(item.objectName());
            }
            log.info("列出文件成功 => Bucket: {}", bucketName);
        } catch (Exception e) {
            log.error("列出文件失败 => Bucket: {}, Error: {}", bucketName, e.getMessage());
        }
        return fileList;
    }

    /**
     * 检查存储桶是否存在，如果不存在则创建。
     *
     * @return 如果存储桶存在或创建成功，返回 true，否则返回 false。
     */
    public static boolean ensureBucketExists() {
        try {
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(MINIO_BUCKET_NAME).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(MINIO_BUCKET_NAME).build());
                log.info("存储桶创建成功 => Bucket: {}", MINIO_BUCKET_NAME);
            }

            // 设置存储桶为公共访问权限
            String readOnlyPolicy = "{\n" +
                    "    \"Version\": \"2012-10-17\",\n" +
                    "    \"Statement\": [\n" +
                    "        {\n" +
                    "            \"Effect\": \"Allow\",\n" +
                    "            \"Principal\": \"*\",\n" +
                    "            \"Action\": [\n" +
                    "                \"s3:GetObject\"\n" +
                    "            ],\n" +
                    "            \"Resource\": [\n" +
                    "                \"arn:aws:s3:::" + MINIO_BUCKET_NAME + "/*\"\n" +
                    "            ]\n" +
                    "        }\n" +
                    "    ]\n" +
                    "}";

            // 设置存储桶策略
            minioClient.setBucketPolicy(
                    SetBucketPolicyArgs.builder()
                            .bucket(MINIO_BUCKET_NAME)
                            .config(readOnlyPolicy)
                            .build()
            );

            log.info("存储桶已设置为公共访问权限 => Bucket: {}", MINIO_BUCKET_NAME);
            log.info("默认存储桶已存在 => Bucket: {}", MINIO_BUCKET_NAME);
            return true;
        } catch (Exception e) {
            log.error("检查或创建存储桶失败 => Bucket: {}, Error: {}", MINIO_BUCKET_NAME, e.getMessage());
            return false;
        }
    }

    /**
     * 获取存储桶列表。
     *
     * @return 存储桶名称列表。
     */
    public static List<String> listBuckets() {
        List<String> bucketList = new ArrayList<>();
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            for (Bucket bucket : buckets) {
                bucketList.add(bucket.name());
            }
            log.info("列出所有存储桶成功");
        } catch (Exception e) {
            log.error("列出存储桶失败 => Error: {}", e.getMessage());
        }
        return bucketList;
    }

    /**
     * 下载文件从 Minio 存储到本地。
     *
     * @param bucketName   Minio 存储桶的名称。
     * @param objectName   要下载的对象名称。
     * @param destination  下载后的本地文件路径。
     * @return 如果下载成功返回 true，否则返回 false。
     */
    public static boolean download(String bucketName, String objectName, String destination) {
        try {
            minioClient.downloadObject(DownloadObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .filename(destination)
                    .build());
            log.info("文件下载成功 => Bucket: {}, Object: {}, Destination: {}", bucketName, objectName, destination);
            return true;
        } catch (Exception e) {
            log.error("下载文件失败 => Bucket: {}, Object: {}, Error: {}", bucketName, objectName, e.getMessage());
            return false;
        }
    }

    private static String detectContentType(File file) {
        String fileName = file.getName().toLowerCase();
        if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
            return "image/jpeg";
        } else if (fileName.endsWith(".png")) {
            return "image/png";
        } else if (fileName.endsWith(".gif")) {
            return "image/gif";
        } else if (fileName.endsWith(".pdf")) {
            return "application/pdf";
        } else if (fileName.endsWith(".zip")) {
            return "application/zip";
        }
        // 默认返回二进制流类型
        return "application/octet-stream";
    }
}
